package org.wx.io.nio.chart;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;

import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import static org.wx.io.nio.chart.UserStatus.*;

@Slf4j
public class NIOChartServer {


    static Selector selector;
    static ServerSocketChannel serverSocketChannel;
    static int port = 9999;
    // 用于存储所有的与客户端通信的SocketChannel
    static Set<SocketChannel> socketChannelSet = new HashSet<>();

    public static void main(String[] args){

        try {
            // 1. 开启通道
            serverSocketChannel = ServerSocketChannel.open();
            // 2. 设置为阻塞
            serverSocketChannel.configureBlocking(false);
            // 3. 绑定端口 ，开启监听
            serverSocketChannel.bind(new InetSocketAddress(port));
            // 4. 开启多路复用器（选择器）
            selector = Selector.open();
            // 5. 将通道注册到选择器中
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            while (true) {
                //log.info("当前的注册的通道数{}",selector.keys().size());
                // 获取发生状态变更的SelectedKey
                while (selector.select(500) > 0) {
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();
                    Iterator<SelectionKey> iterator = selectionKeys.iterator();

                    while (iterator.hasNext()) {
                        // 获取SelectionKey
                        SelectionKey key = iterator.next();
                        // 删除，防止重复操作
                        iterator.remove();
                        // 判断key的状态
                        if (key.isAcceptable()) { //ServerSocketChannel 有状态的变更
                            ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
                            // 新建一个socket用于与客户端通信
                            SocketChannel clientSocket = serverSocketChannel.accept();
                            log.info("有新同学上线:{},大家欢迎...", clientSocket.getRemoteAddress());

                            clientSocket.configureBlocking(false);
                            // 将通信的socket注册到选择器中 监听它的可读状态
                            clientSocket.register(selector, SelectionKey.OP_READ,ByteBuffer.allocate(4096));
                            // 添加到集合
                            socketChannelSet.add(clientSocket);
                            broadcast(clientSocket,LOGIN);

                        } else if (key.isReadable()) { //与客户端通信的socket的状态变成可读，证明客户端发送数据了
                            // 接受数据
                            SocketChannel clientSocket = (SocketChannel) key.channel();

                            ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
                            // 将数据读到bytebuffer中
                            byteBuffer.clear();
                            try {
                                clientSocket.read(byteBuffer);
                            } catch (IOException e) {
                                //e.printStackTrace();
                                //在close方法内就已经调用了key.cancel()   key.cancel();
                                log.warn("{}连接断开",clientSocket.getRemoteAddress());
                                // 将集合中的删除
                                socketChannelSet.remove(clientSocket);
                                broadcast(clientSocket,LOGOUT);
                                clientSocket.close();
                                continue;
                            }


                            // 对于客户端进行写出数据
                            broadcast(byteBuffer,clientSocket,SPEAK);
                        }

                    }

                }

            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        }

    }

    /**
     * 广播方法
     */
    static  void  broadcast(ByteBuffer byteBuffer,SocketChannel speaker,UserStatus status){
        try {
            Iterator<SocketChannel> sit = socketChannelSet.iterator();
            while (sit.hasNext()) {
                SocketChannel socketChannel = sit.next();



                byte[] bytes;
                switch (status) {
                    case LOGIN :
                        bytes= ("有新同学上线:"+speaker.socket().getPort()+",大家欢迎...").getBytes();
                        break;
                    case SPEAK:
                        bytes = (speaker.socket().getPort() + "说：").getBytes();
                        if(socketChannel == speaker){ //自己不广播
                            continue;
                        }
                        break;
                    case LOGOUT:
                        bytes= ("同学下线:"+speaker.socket().getPort()+",欢迎下次光临...").getBytes();
                        break;
                    default:
                        throw new IllegalStateException("Unexpected value: " + status);
                }
                ByteBuffer dist = ByteBuffer.allocate(4096);
                dist.put(bytes);
                // 判断是不是首次登录
                if(byteBuffer != null){
                    byteBuffer.flip();

                    dist.put(byteBuffer);
                }

                dist.flip();
                socketChannel.write(dist);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 重载广播方法
     * @param speaker
     */
    static  void  broadcast(SocketChannel speaker,UserStatus status){
        broadcast(null,speaker,status);
    }

}
